package com.fdb.efp.esb.service.impl.esb;

import com.fdb.efp.esb.common.client.resp.EsbRespServiceBean;
import com.fdb.efp.esb.common.client.resp.EsbRespSysHeadBean;
import com.fdb.basic.framework.core.exception.ESBException;
import com.fdb.basic.framework.core.util.StringUtil;
import com.fdb.efp.esb.service.client.ESBBeanCreator;
import com.fdb.efp.esb.service.client.ESBClientFactory;
import com.fdb.efp.esb.service.facade.esb.EsbService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;
import java.util.function.Function;

/**
 * 带重试功能的ESB服务调用模板
 *
 * @author F嘉阳
 * @date 2020/5/12 16:11
 */
public abstract class AbstractRetryBaseEsbService<Q, P> implements EsbService<Q, P> {

    private static Logger log = LoggerFactory.getLogger(AbstractRetryBaseEsbService.class);

    // 日志ID
    private static final String LOGGER_ID = "ESB服务调用（带重试）";

    private static final ThreadLocal<String> SEQ = ThreadLocal.withInitial(() -> "流水号：");

    /**
     * 对queryReports进行预处理（可选),由子类实现
     *
     * @param reqBean
     */
    protected void preProcess(Q reqBean) {
    }

    ;

    /**
     * 流水号关联
     *
     * @return 流水号
     */
    protected abstract String applySeq(Q reqBean);

    /**
     * 对接口返回结果进行处理，并返回包装后的结果
     *
     * @param reqBean
     * @param resBean
     * @return
     * @throws Exception
     */
    protected abstract P postProcess(Q reqBean, P resBean) throws Exception;

    /**
     * 各接口的交易码
     * @return
     */
    protected abstract String tradeNo();

    /**
     * 各接口的服务场景
     *
     * @return
     */
    protected abstract String scene();

    /**
     * 接口实现需提供将 返回JSON 转换为 Bean 的转换策略
     *
     * @return
     */
    protected abstract Function<String, P> converter();

    /**
     * ESBBeanCreator ，用于配置产品机构号，请求头等信息
     *
     * @return
     */
    protected abstract ESBBeanCreator beanFactory();

    /**
     * 需要重试的业务逻辑
     *
     * @param reqBean
     * @return
     * @throws Exception
     */
    private P invokeService(Q reqBean) throws Exception {
        log.info(LOGGER_ID, SEQ.get() + "调用ESB服务开始， 请求参数：" + reqBean);

        preProcess(reqBean);

        try {
            EsbRespServiceBean esbRespServiceBean = ESBClientFactory.buildClient()
                    .withBody(reqBean)
                    .withTradeNo(tradeNo())
                    .withScene(scene())
                    .withESBBeanFactory(beanFactory())
                    .create()
                    .sendAndReceive();
            EsbRespSysHeadBean esbRespSysHeadBean = esbRespServiceBean.getSysHead();
            P resBean = esbRespServiceBean.getBody(converter());
            if (!StringUtils.equals(esbRespSysHeadBean.getRetInfArry().get(0).getRetCd(),"000000")) {
                StringBuilder errMsg = new StringBuilder("调用ESB服务异常");
                Optional.of(esbRespSysHeadBean).map(e -> e.getRetInfArry().get(0).getRetMsg())
                        .ifPresent(s -> errMsg.append(",错误信息: ").append(s));
                throw new ESBException(errMsg.toString());
            }
            return postProcess(reqBean, resBean);
        } catch (ESBException e) {
            log.error(LOGGER_ID, SEQ.get() + "ESB服务调用失败，ESB异常信息：" + e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error(LOGGER_ID, SEQ.get() + "ESB服务调用失败，异常信息：" + e.getMessage());
            throw new ESBException(e);
        }
    }

    /**
     * 等待
     */
    private void timeWait(long t) {
        try {
            Thread.sleep(t);
        } catch (InterruptedException e) {
            log.error(LOGGER_ID, SEQ.get() + "等待失败", e);
        }
    }

    /**
     * 重试逻辑
     *
     * @param reqBean
     * @return 若重试后仍然失败则返回空对象，注意空指针处理
     */
    @Override
    public final P service(Q reqBean) throws Exception {
        if (StringUtil.isNotEmpty(applySeq(reqBean))) {
            SEQ.set(SEQ.get() + applySeq(reqBean) + ",");
        }
        int retryTimes = retryTimes();
        long nextRetryTime = waitTime();
        try {
            while (retryTimes > 0) {
                try {
                    P res = this.invokeService(reqBean);
                    if (isSuccess(res)) {
                        // 未发生异常则退出
                        log.info(LOGGER_ID, SEQ.get() + "调用成功");
                        return res;
                    } else {
                        log.error(LOGGER_ID, SEQ.get() + "成功判断不通过，剩余重试次数：" + (retryTimes - 1));
                        nextRetryTime = retry(retryTimes, nextRetryTime);
                    }
                } catch (Throwable throwable) {
                    log.error(LOGGER_ID, SEQ.get() + "剩余重试次数：" + (retryTimes - 1), throwable);
                    nextRetryTime = retry(retryTimes, nextRetryTime);
                } finally {
                    retryTimes--;
                }
            }
            if (retryTimes == 0) {
                log.info(LOGGER_ID, SEQ.get() + "重试结束，重试次数已用完，回调重试后处理方法");
                this.retryFailCallback(reqBean);
                return null;
            }
            log.info(LOGGER_ID, SEQ.get() + "剩余重试次数：" + retryTimes + ", 重试结束");
            return null;
        } finally {
            // 使用后移除
            SEQ.remove();
        }
    }

    private long retry(int retryTimes, long nextRetryTime) {
        // 等待
        nextRetryTime = nextRetryTime * timeSpan();
        if (retryTimes - 1 != 0) {
            log.info(LOGGER_ID, SEQ.get() + "下一次重试时间：" + nextRetryTime);
            this.timeWait(nextRetryTime);
        }
        return nextRetryTime;
    }

    /**
     * 执行次数，包括第一次执行
     * 若为3，则第一次执行出错后还会执行2次重试
     *
     * @return
     */
    protected Integer retryTimes() {
        return 3;
    }

    /**
     * 等待时间
     *
     * @return 单位：毫秒
     */
    protected Long waitTime() {
        return 5000L;
    }

    /**
     * 等待时间跨度
     * 默认为1，若等待时间为5s，跨度为1，则每次间隔的等待时间为5*1=5s, 5*1=5s, 10*1=5s
     * 若等待时间为5s，跨度为2，则每次间隔的等待时间为5*1=5s, 5*2=10s, 10*2=20s
     * 若等待时间为5s，跨度为3，则每次间隔的等待时间为5*1=5s, 5*3=15s, 10*3=30s
     *
     * @return
     */
    protected Long timeSpan() {
        return 1L;
    }

    /**
     * 重试失败后执行的操作
     */
    protected void retryFailCallback(Q reqBean) throws Exception {
    }

    /**
     * 允许自定义成功和失败的判断标准
     *
     * @param res 调用返回值
     * @return
     */
    protected boolean isSuccess(P res) {
        return true;
    }

}
